package com.cloudkinto.service.storage.impl;

import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.bo.storage.*;
import com.cloudkinto.common.BeanConvert;
import com.cloudkinto.common.ExcelExportTitleStyle;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.ExcelUtils;
import com.cloudkinto.common.utils.StringsUtil;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.ProductStockRackDao;
import com.cloudkinto.dao.StorageAreaDao;
import com.cloudkinto.dao.StorageLocationDao;
import com.cloudkinto.dto.storage.StorageLocationAddImportDto;
import com.cloudkinto.dto.storage.StorageLocationExportDto;
import com.cloudkinto.dto.storage.StorageLocationUpdateImportDto;
import com.cloudkinto.entity.OutboundPickingDo;
import com.cloudkinto.entity.ProductStockRackDo;
import com.cloudkinto.entity.StorageAreaDo;
import com.cloudkinto.entity.StorageLocationDo;
import com.cloudkinto.enumType.order.outbound.OutboundOrderKind;
import com.cloudkinto.enumType.storage.StorageAreaEnum;
import com.cloudkinto.service.order.picking.OutboundPickingService;
import com.cloudkinto.service.storage.StorageLocationService;
import com.cloudkinto.vo.common.CommonBatchOperationVo;
import com.cloudkinto.vo.storage.CellAvailableVo;
import com.cloudkinto.vo.storage.LocationRes;
import com.cloudkinto.vo.storage.StorageLocationVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <p>
 * 库位表 service实现类
 * </p>
 *
 * @author long
 * @since 2024-11-12
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class StorageLocationServiceImpl extends ServiceImpl<StorageLocationDao, StorageLocationDo> implements StorageLocationService {

    public static Map<String, Integer> storageLocationTypeMap = new HashMap<>();

    static {
        storageLocationTypeMap.put("货架", 1);
        storageLocationTypeMap.put("地摊", 2);
    }

    private final StorageAreaDao storageAreaDao;
    private final ProductStockRackDao productStockRackDao;
    private final OutboundPickingService pickingService;

    /**
     * 用于stream流的filter方法做实体类的某个属性去重
     */
    static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        //putIfAbsent方法添加键值对，如果map集合中没有该key对应的值，则直接添加，并返回null，如果已经存在对应的值，则依旧为原来的值。
        //如果返回null表示添加数据成功(不重复)，不重复(null==null :TRUE)
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    @Override
    public void add(StorageLocationAddBo req, Long tenantId) {
//        StorageLocationDo entityDo = new StorageLocationDo();
//        BeanUtils.copyProperties(req, entityDo);
        StorageLocationDo entityDo = BeanConvert.INSTANCE.locationAddBoToLocationDo(req);
        entityDo.setTenantId(tenantId);
        entityDo.setWhId(req.getWhId());
        entityDo.setEmptyLocationFlag(Boolean.TRUE);
        baseMapper.insert(entityDo);
    }

    @Override
    public void update(StorageLocationUpdateBo req, Long userId) {
        StorageLocationDo entityDo = new StorageLocationDo();
//        BeanUtils.copyProperties(req, entityDo);
        BeanConvert.INSTANCE.locationUpdateBoToLocationDo(req, entityDo);
        baseMapper.updateById(entityDo);
    }

    @Override
    public CommonBatchOperationVo delete(List<Long> ids) {
        CommonBatchOperationVo res = new CommonBatchOperationVo();
        res.setTotal(ids.size());
        Map<Long, List<ProductStockRackDo>> rackIdMatchList = productStockRackDao.selectList(
                Wrappers.<ProductStockRackDo>lambdaQuery()
                        .in(ProductStockRackDo::getRackId, ids)
        ).stream().collect(Collectors.groupingBy(ProductStockRackDo::getRackId));
        Map<Long, StorageLocationDo> locationIdMatchDo = baseMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(StorageLocationDo::getId, val -> val));
        for (Long id : ids) {
            List<ProductStockRackDo> productStockRackDoList = rackIdMatchList.get(id);
            if (CollUtil.isNotEmpty(productStockRackDoList)) {
                StorageLocationDo storageLocationDo = locationIdMatchDo.get(id);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", storageLocationDo.getCode());
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Storage_Location_Has_Stock_Del_Failed, storageLocationDo.getCode()));
                res.getFailedList().add(map);
                res.setFailed(res.getFailed() + 1);
            } else {
                baseMapper.deleteById(id);
                res.setSuccess(res.getSuccess() + 1);
            }
        }
        return res;
    }

    @Override
    public StorageLocationDo detail(Long id) {
        return baseMapper.selectById(id);
    }

    @Override
    public SingleResult<Map<String, Object>> pageInit(Long tenantId, Long whId) {
        HashMap<String, Object> map = new HashMap<>(2);
        map.put("storageLocationType", storageLocationTypeMap);
        map.put("storageAreaList", storageAreaDao.selectList(Wrappers.<StorageAreaDo>lambdaQuery().eq(StorageAreaDo::getTenantId, tenantId).eq(Objects.nonNull(whId), StorageAreaDo::getWhId, whId)));
        map.put("storageAreaType", StorageAreaEnum.getMapList());
        return SingleResult.success(map);
    }

    @Override
    public PageResult<StorageLocationVo> getListPage(StorageLocationQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<StorageLocationVo> list = baseMapper.getPageList(bo);
        PageInfo<StorageLocationVo> page = new PageInfo<>(list);
        return new PageResult<>(list, current, pageSize, (int) page.getTotal(), page.getPages());
    }

    @Override
    public CommonBatchOperationVo importAdd(StorageImportBo bo, Long tenantId) {
        CommonBatchOperationVo res = new CommonBatchOperationVo();
        List<StorageLocationAddImportDto> importDtoList;
        try {
            importDtoList = ExcelUtils.importExcel(bo.getFile(), 1, 0, StorageLocationAddImportDto.class);
        } catch (Exception e) {
            throw new BizException(SysConstant.Excel_Template_Error);
        }
        int oldSize = importDtoList.size();
        res.setTotal(importDtoList.size());
        //根据库区编码去重
        importDtoList = importDtoList.stream().filter(
                i -> StrUtil.isNotBlank(i.getAreaCode())
                     && StrUtil.isNotBlank(i.getCode())
                     && Objects.nonNull(i.getType())
        ).collect(Collectors.toList());
        int newSize = importDtoList.size();
        if (oldSize != newSize) {
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "Exception");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Import_Required_Field_Blank));
            res.setFailed(res.getFailed() + oldSize - newSize);
            res.getFailedList().add(map);
        }
        if (importDtoList.isEmpty()) {
            return res;
        }
        importDtoList = importDtoList.stream().filter(
                distinctByKey(StorageLocationAddImportDto::getCode)
        ).collect(Collectors.toList());
        Map<String, StorageAreaDo> areaCodeMatchMap = storageAreaDao.selectList(
                Wrappers.<StorageAreaDo>lambdaQuery()
                        .eq(StorageAreaDo::getTenantId, tenantId)
                        .in(
                                StorageAreaDo::getCode,
                                importDtoList.stream()
                                        .map(StorageLocationAddImportDto::getAreaCode)
                                        .collect(Collectors.toList())
                        )
        ).stream().collect(Collectors.toMap(StorageAreaDo::getCode, val -> val));
        Map<String, StorageLocationDo> codeMatch = baseMapper.selectList(Wrappers.<StorageLocationDo>lambdaQuery()
                .eq(StorageLocationDo::getTenantId, tenantId)
                .eq(StorageLocationDo::getWhId, bo.getWhId())
                .in(StorageLocationDo::getCode, importDtoList.stream().map(StorageLocationAddImportDto::getCode).collect(Collectors.toList()))
        ).stream().collect(Collectors.toMap(StorageLocationDo::getCode, val -> val));
        res.setTotal(importDtoList.size());
        for (StorageLocationAddImportDto storageLocationAddImportDto : importDtoList) {
//            StorageLocationDo entityDo = new StorageLocationDo();
//            BeanUtils.copyProperties(storageLocationAddImportDto, entityDo);
            StorageLocationDo entityDo = BeanConvert.INSTANCE.locationAddImportDtoToLocationDo(storageLocationAddImportDto);
            entityDo.setTenantId(tenantId);
            entityDo.setWhId(bo.getWhId());
            entityDo.setEmptyLocationFlag(Boolean.TRUE);
            StorageAreaDo storageAreaDo = areaCodeMatchMap.get(storageLocationAddImportDto.getAreaCode());
            StorageLocationDo storageLocationDo = codeMatch.get(storageLocationAddImportDto.getCode());
            if (Objects.nonNull(storageLocationDo)) {
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("areaCode", storageLocationAddImportDto.getAreaCode());
                map.put("errorMsg", "Position Code/库位编码[" + storageLocationAddImportDto.getAreaCode() + "]Already exists");
                res.getFailedList().add(map);
                res.setFailed(res.getFailed() + 1);
            } else if (Objects.isNull(storageAreaDo)) {
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("areaCode", storageLocationAddImportDto.getAreaCode());
                map.put("errorMsg", "Zone/所属库区编码[" + storageLocationAddImportDto.getAreaCode() + "]Not Found");
                res.getFailedList().add(map);
                res.setFailed(res.getFailed() + 1);
            } else {
                entityDo.setAreaId(storageAreaDo.getId());
                baseMapper.insert(entityDo);
                res.setSuccess(res.getSuccess() + 1);
            }
        }
        return res;
    }

    @Override
    public CommonBatchOperationVo importUpdate(StorageImportBo bo, Long tenantId) {
        CommonBatchOperationVo res = new CommonBatchOperationVo();
        List<StorageLocationUpdateImportDto> importDtoList;
        try {
            importDtoList = ExcelUtils.importExcel(bo.getFile(), 1, 0, StorageLocationUpdateImportDto.class);
        } catch (Exception e) {
            throw new BizException(SysConstant.Excel_Template_Error);
        }
        int oldSize = importDtoList.size();
        res.setTotal(importDtoList.size());
        //根据库区编码去重
        importDtoList = importDtoList.stream().filter(
                i -> StrUtil.isNotBlank(i.getCode())
        ).collect(Collectors.toList());
        int newSize = importDtoList.size();
        if (oldSize != newSize) {
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "Exception");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Import_Required_Field_Blank));
            res.setFailed(res.getFailed() + oldSize - newSize);
            res.getFailedList().add(map);
        }
        if (importDtoList.isEmpty()) {
            return res;
        }
        importDtoList = importDtoList.stream().filter(
                distinctByKey(StorageLocationUpdateImportDto::getCode)
        ).collect(Collectors.toList());
        for (StorageLocationUpdateImportDto importDto : importDtoList) {
            boolean updateFlag = this.lambdaUpdate()
                    .eq(StorageLocationDo::getCode, importDto.getCode())
                    .set(StorageLocationDo::getPickingLocationSort, importDto.getPickingLocationSort())
                    .set(StorageLocationDo::getPickingRecommendSort, importDto.getPickingRecommendSort())
                    .update();
            if (updateFlag) {
                res.setSuccess(res.getSuccess() + 1);
            } else {
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", importDto.getCode());
                map.put("errorMsg", "Location Code/库位编码[" + importDto.getCode() + "]Not Found");
                res.getFailedList().add(map);
                res.setFailed(res.getFailed() + 1);
            }
        }
        return res;
    }

    @Override
    public void export(List<Long> ids, Long tenantId, HttpServletResponse response) throws IOException {
        List<StorageLocationVo> list = baseMapper.getList(ids, tenantId);
        //Excel文件导出配置
        ExportParams excelParam = new ExportParams();
        excelParam.setType(ExcelType.HSSF);
        excelParam.setStyle(ExcelExportTitleStyle.class);
        List<StorageLocationExportDto> exportDtoList = new ArrayList<>();
        if (CollUtil.isNotEmpty(list)) {
            exportDtoList = JSONUtil.toList(JSONUtil.toJsonStr(list), StorageLocationExportDto.class);
            exportDtoList.forEach(i -> {
                i.setAreaType(StorageAreaEnum.getAreaEnum(Integer.valueOf(i.getAreaType())).getTypeName());
                i.setTypeName(i.getType() == 1 ? "货架" : i.getType() == 2 ? "地摊" : "");
            });
        }
        ExcelUtils.exportExcel(
                exportDtoList,
                StorageLocationExportDto.class,
                "Location_Export" + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN),
                excelParam,
                response,
                "xls"
        );
    }

    @Override
    public List<CellAvailableVo> getCellAvailableByProductId(CellAvailableBySkuQueryBo bo) {
        List<CellAvailableVo> availableDtoList = baseMapper.getCellAvailableBySku(bo);
        Long outboundProductId = bo.getOutboundProductId();
        if (Objects.nonNull(outboundProductId)) {
            //需要加上该出库单的该sku推荐的库位待拣货的数据
            List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                    .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                    .eq(OutboundPickingDo::getOutboundProductId, outboundProductId)
                    .eq(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                    .list();
            for (OutboundPickingDo pickingDo : pickingDoList) {
                Long rackId = pickingDo.getRackId();
                Integer qty = pickingDo.getQty();
                for (CellAvailableVo availableDto : availableDtoList) {
                    if (rackId.equals(availableDto.getCellId())) {
                        availableDto.setAvailableQty(availableDto.getAvailableQty() + qty);
                    }
                }
            }
        }
        //波次号
        Long wareId = bo.getWareId();
        if (Objects.nonNull(wareId)) {
            //查询波次待拣货的推荐库位拣货信息
            List<CellAvailableVo> availableDtoListByWareId = baseMapper.getCellAvailableByWareId(wareId);
            for (CellAvailableVo dtoByWaveId : availableDtoListByWareId) {
                for (CellAvailableVo availableDto : availableDtoList) {
                    if (dtoByWaveId.getCellId().equals(availableDto.getCellId())) {
                        availableDto.setAvailableQty(availableDto.getAvailableQty() + dtoByWaveId.getAvailableQty());
                    }
                }
            }
        }
        //调拨发货计划
        Long allocatePlanItemId = bo.getAllocatePlanItemId();
        if (Objects.nonNull(allocatePlanItemId)) {
            //需要加上该调拨计划的该sku推荐的库位待拣货的数据
            List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                    .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                    .eq(OutboundPickingDo::getOutboundProductId, allocatePlanItemId)
                    .eq(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                    .list();
            for (OutboundPickingDo pickingDo : pickingDoList) {
                Long rackId = pickingDo.getRackId();
                Integer qty = pickingDo.getQty();
                for (CellAvailableVo availableDto : availableDtoList) {
                    if (rackId.equals(availableDto.getCellId())) {
                        availableDto.setAvailableQty(availableDto.getAvailableQty() + qty);
                    }
                }
            }
        }
        //去除可用数量为0的数据
        availableDtoList = availableDtoList.stream()
                .filter(i -> i.getAvailableQty() > 0)
                .collect(Collectors.toList());
        return availableDtoList;
    }

    @Override
    public List<CellAvailableVo> getCellAvailableByBox(CellAvailableByBoxQueryBo bo) {
        List<CellAvailableVo> availableDtoList = baseMapper.getCellAvailableByBox(bo);
        Long outboundProductId = bo.getOutboundProductId();
        if (Objects.nonNull(outboundProductId)) {
            //需要加上该出库单的该sku推荐的库位待拣货的数据
            List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                    .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                    .eq(OutboundPickingDo::getOutboundProductId, outboundProductId)
                    .eq(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                    .list();
            for (OutboundPickingDo pickingDo : pickingDoList) {
                Long rackId = pickingDo.getRackId();
                Integer qty = pickingDo.getQty();
                for (CellAvailableVo availableDto : availableDtoList) {
                    if (rackId.equals(availableDto.getCellId())) {
                        availableDto.setAvailableQty(availableDto.getAvailableQty() + qty);
                    }
                }
            }
        }
        //去除可用数量为0的数据
        availableDtoList = availableDtoList.stream()
                .filter(i -> i.getAvailableQty() > 0)
                .collect(Collectors.toList());
        return availableDtoList;
    }

//    /**
//     * 查询分页列表之后对列表数据的处理
//     */
//    private List<StorageLocationDto> handlerListAfter(List<StorageLocationDo> list) {
//        List<StorageLocationDto> result = BeanCopierUtils.listCopyMapper(list, StorageLocationDo.class, StorageLocationDto.class);
//        if (CollUtil.isNotEmpty(result)) {
//            List<Long> areaIds = result.stream().map(StorageLocationDto::getAreaId).collect(Collectors.toList());
//            Map<Long, StorageAreaDo> areaDoMap = storageAreaDao.selectList(
//                    Wrappers.<StorageAreaDo>lambdaQuery()
//                            .in(StorageAreaDo::getId, areaIds)
//            ).stream().collect(Collectors.toMap(StorageAreaDo::getId, val -> val));
//            result.forEach(val -> {
//                StorageAreaDo areaDo = areaDoMap.get(val.getAreaId());
//                val.setAreaName(areaDo.getName());
//                val.setAreaCode(areaDo.getCode());
//                val.setAreaType(StorageAreaEnum.getAreaEnum(areaDo.getType()).getTypeName());
//            });
//        }
//        return result;
//    }

//    private LambdaQueryWrapper<StorageLocationDo> queryBuild(StorageLocationQueryBo bo) {
//        LambdaQueryWrapper<StorageLocationDo> lqw = new LambdaQueryWrapper<>();
//        lqw.in(CollUtil.isNotEmpty(bo.getTypeList()), StorageLocationDo::getType, bo.getTypeList());
//        lqw.eq(Objects.nonNull(bo.getAreaId()), StorageLocationDo::getAreaId, bo.getAreaId());
//        lqw.eq(Objects.nonNull(bo.getEmpty()), StorageLocationDo::getEmptyLocationFlag, bo.getEmpty());
//        lqw.like(StrUtil.isNotBlank(bo.getCode()), StorageLocationDo::getCode, bo.getCode());
//        lqw.eq(Objects.nonNull(bo.getPriority()), StorageLocationDo::getPickingRecommendSort, bo.getPriority());
//        lqw.last(
//                StrUtil.isNotBlank(bo.getSortName()),
//                "order by " + bo.getSortName() + " " + bo.getSortOrder()
//        );
//        return lqw;
//    }


    //根据仓库获取推荐上架库位
    @Override
    public LocationRes getPickingRecommendRack(int stockAttribute, Long whId) {
        return baseMapper.getPickingRecommendRack(stockAttribute, whId);
    }
}
